package simpledb.tx;

import simpledb.server.SimpleDB;
import simpledb.file.Block;
import simpledb.buffer.*;
import simpledb.tx.recovery.RecoveryMgr;
import simpledb.tx.concurrency.ConcurrencyMgr;

/**
 * Provides transaction management for clients, ensuring that all transactions
 * are serializable, recoverable, and in general satisfy the ACID properties.
 * <br>
 * 为客户端提供事务管理，确保所有事务都可序列化，可恢复，并且通常满足ACID[原子性（Atomicity）、一致性（Consistency）、隔离性（
 * Isolation）、持久性（Durability）]属性。
 * 
 * <pre>
 *  1 Concurrency保证了Consistency和Isolation
 *    a) 通过concurrencyMgr给数据块加sLock
 *    b) 通过BufferList对象获取buffer，用来读数据
 *    c) 通过buffer读取数据
 *  2 Recovery保证了Atomicity和Durability
 *    a) 通过concurrencyMgr给数据块加xLock
 *    b) 通过BufferList对象获取buffer，用来写数据
 *    c) 先写日志，通过recoveryMgr写日志，并保存将写入数据位置存有的旧数据
 *    d) 通过buffer写新数据
 * </pre>
 * 
 * @author Edward Sciore
 */
public class Transaction {
  private static int nextTxNum = 0;
  private static final int END_OF_FILE = -1;
  private RecoveryMgr recoveryMgr;
  private ConcurrencyMgr concurMgr;
  private int txnum;
  private BufferList myBuffers = new BufferList();

  /**
   * Creates a new transaction and its associated recovery and concurrency
   * managers. This constructor depends on the file, log, and buffer managers
   * that it gets from the class {@link simpledb.server.SimpleDB}. Those objects
   * are created during system initialization. Thus this constructor cannot be
   * called until either {@link simpledb.server.SimpleDB#init(String)} or
   * {@link simpledb.server.SimpleDB#initFileLogAndBufferMgr(String)} or is
   * called first.<br>
   * 创建新事务及其关联的恢复和并发管理器。 这个构造函数依赖于从类{@link simpledb.server.SimpleDB}
   * 中获得的文件、日志和缓冲区管理器。 这些对象在系统初始化期间创建。 因此，直到
   * {@link simpledb.server.SimpleDB#init(String)}或
   * {@link simpledb.server.SimpleDB#initFileLogAndBufferMgr(String)}.或
   * {@link simpledb.server.SimpleDB#initFileLogAndBufferMgr(String)}
   * 被首先调用之前，才能调用此构造函数。
   */
  public Transaction() {
    txnum = nextTxNumber();
    recoveryMgr = new RecoveryMgr(txnum);
    concurMgr = new ConcurrencyMgr();
  }

  /**
   * Commits the current transaction. Flushes all modified buffers (and their
   * log records), writes and flushes a commit record to the log, releases all
   * locks, and unpins any pinned buffers.<br>
   * 提交当前事务。 刷新所有修改的缓冲区（及其日志记录），写入并将提交记录刷新到日志中，释放所有锁定，并取消固定任何固定缓冲区。
   */
  public void commit() {
    recoveryMgr.commit();
    concurMgr.release();
    myBuffers.unpinAll();
    System.out.println("transaction " + txnum + " committed");
  }

  /**
   * Rolls back the current transaction. Undoes any modified values, flushes
   * those buffers, writes and flushes a rollback record to the log, releases
   * all locks, and unpins any pinned buffers.<br>
   * 回滚当前事务。 撤消所有修改的值，刷新这些缓冲区，写入并将回滚记录刷新到日志中，释放所有锁定，并取消固定任何固定缓冲区。
   */
  public void rollback() {
    recoveryMgr.rollback();
    concurMgr.release();
    myBuffers.unpinAll();
    System.out.println("transaction " + txnum + " rolled back");
  }

  /**
   * Flushes all modified buffers. Then goes through the log, rolling back all
   * uncommitted transactions. Finally, writes a quiescent checkpoint record to
   * the log. This method is called only during system startup, before user
   * transactions begin.<br>
   * 刷新所有修改的缓冲区。 然后通过日志，回滚所有未提交的事务。 最后，将静态检查点记录写入日志。 此方法仅在系统启动期间（在用户事务开始之前）调用。
   */
  public void recover() {
    SimpleDB.bufferMgr().flushAll(txnum);
    recoveryMgr.recover();
  }

  /**
   * Pins the specified block. The transaction manages the buffer for the
   * client.<br>
   * 引导指定的块。 事务管理客户端的缓冲区。
   * 
   * @param blk a reference to the disk block
   */
  public void pin(Block blk) {
    myBuffers.pin(blk);
  }

  /**
   * Unpins the specified block. The transaction looks up the buffer pinned to
   * this block, and unpins it.<br>
   * 取消指定指定块。 事务查找固定到此块的缓冲区，并取消固定它。
   * 
   * @param blk a reference to the disk block
   */
  public void unpin(Block blk) {
    myBuffers.unpin(blk);
  }

  /**
   * Returns the integer value stored at the specified offset of the specified
   * block. The method first obtains an SLock on the block, then it calls the
   * buffer to retrieve the value.<br>
   * 返回存储在指定块的指定偏移处的整数值。 该方法首先获取块上的SLock，然后调用缓冲区来检索该值。
   * 
   * @param blk a reference to a disk block 磁盘块的引用
   * @param offset the byte offset within the block 块内的字节偏移
   * @return the integer stored at that offset 存储在偏移量的整数值
   */
  public int getInt(Block blk, int offset) {
    concurMgr.sLock(blk);
    Buffer buff = myBuffers.getBuffer(blk);
    return buff.getInt(offset);
  }

  /**
   * Returns the string value stored at the specified offset of the specified
   * block. The method first obtains an SLock on the block, then it calls the
   * buffer to retrieve the value.<br>
   * 返回存储在指定块的指定偏移处的字符串值。 该方法首先获取块上的SLock，然后调用缓冲区来检索该值。
   * 
   * @param blk a reference to a disk block
   * @param offset the byte offset within the block
   * @return the string stored at that offset
   */
  public String getString(Block blk, int offset) {
    concurMgr.sLock(blk);
    Buffer buff = myBuffers.getBuffer(blk);
    return buff.getString(offset);
  }

  /**
   * Stores an integer at the specified offset of the specified block. The
   * method first obtains an XLock on the block. It then reads the current value
   * at that offset, puts it into an update log record, and writes that record
   * to the log. Finally, it calls the buffer to store the value, passing in the
   * LSN of the log record and the transaction's id.<br>
   * 在指定块的指定偏移处存储整数。 该方法首先获得块上的XLock。 然后，它读取该偏移处的当前值，将其放入更新日志记录，并将该记录写入日志。
   * 最后，它调用缓冲区来存储值，传入日志记录的LSN和事务的ID。
   * 
   * @param blk a reference to the disk block
   * @param offset a byte offset within that block
   * @param val the value to be stored
   */
  public void setInt(Block blk, int offset, int val) {
    concurMgr.xLock(blk);
    Buffer buff = myBuffers.getBuffer(blk);
    // 在恢复管理中把旧值写入日志
    int lsn = recoveryMgr.setInt(buff, offset, val);
    // 写入新的值
    buff.setInt(offset, val, txnum, lsn);
  }

  /**
   * Stores a string at the specified offset of the specified block. The method
   * first obtains an XLock on the block. It then reads the current value at
   * that offset, puts it into an update log record, and writes that record to
   * the log. Finally, it calls the buffer to store the value, passing in the
   * LSN of the log record and the transaction's id.<br>
   * 以指定块的指定偏移量存储字符串。 该方法首先获得块上的XLock。 然后，它读取该偏移处的当前值，将其放入更新日志记录，并将该记录写入日志。
   * 最后，它调用缓冲区来存储值，传入日志记录的LSN和事务的ID。
   * 
   * @param blk a reference to the disk block
   * @param offset a byte offset within that block
   * @param val the value to be stored
   */
  public void setString(Block blk, int offset, String val) {
    concurMgr.xLock(blk);
    Buffer buff = myBuffers.getBuffer(blk);
    int lsn = recoveryMgr.setString(buff, offset, val);
    buff.setString(offset, val, txnum, lsn);
  }

  /**
   * Returns the number of blocks in the specified file. This method first
   * obtains an SLock on the "end of the file", before asking the file manager
   * to return the file size.<br>
   * 返回指定文件中的块数。 此方法首先在“文件结尾”获取一个SLock，然后请求文件管理器返回文件大小。
   * 
   * @param filename the name of the file
   * @return the number of blocks in the file
   */
  public int size(String filename) {
    Block dummyblk = new Block(filename, END_OF_FILE);
    concurMgr.sLock(dummyblk);
    return SimpleDB.fileMgr().size(filename);
  }

  /**
   * Appends a new block to the end of the specified file and returns a
   * reference to it. This method first obtains an XLock on the
   * "end of the file", before performing the append.<br>
   * 将新块附加到指定文件的结尾，并返回对其的引用。 此方法首先在执行附加之前在“文件的结尾”获取XLock。
   * 
   * @param filename the name of the file
   * @param fmtr the formatter used to initialize the new page
   * @return a reference to the newly-created disk block
   */
  public Block append(String filename, PageFormatter fmtr) {
    Block dummyblk = new Block(filename, END_OF_FILE);
    concurMgr.xLock(dummyblk);
    Block blk = myBuffers.pinNew(filename, fmtr);
    unpin(blk);
    return blk;
  }

  private static synchronized int nextTxNumber() {
    nextTxNum++;
    System.out.println("new transaction: " + nextTxNum);
    return nextTxNum;
  }
}
